<?php
require ( 'utility.php' );

define ( 'DBM_TYPE_MYSQL', 1 );

class DBManagerException extends Exception { }
class DBLiteral
{
	private $value;

	function __construct ( $value )
	{
		$this->value = $value;
	}

	public function __toString ()
	{
		return $this->value;
	}
}

class DBManager
{
	private $dbtype;
	private $db;
	private $table_prefix;

	function __construct ( $dbtype )
	{
		$this->dbtype = $dbtype;
	}

	public function connect ( array $params )
	{
		$dsn = $this->_makedsn ( $params );
		$this->db = new PDO ( $dsn, $params [ 'user' ], $params [ 'passwd' ] );
	}

	private function _makedsn ( array $params )
	{
		$dsn = '';
		switch ( $this->dbtype )
		{
			case DBM_TYPE_MYSQL:
				$dsn = sprintf ( 'mysql:host=%s;dbname=%s', $params [ 'host' ], $params [ 'db' ] );
				break;
			default:
				throw new DBManagerException ( 'DB type not supported' );
				
		}
		return $dsn;
	}

	public function select ( $tables, $fields, array $params = array() )
	{
		$conds = array_get ( $params, 'conds' );
		$joins = array_get ( $params, 'joins' );
		$extras = array_get ( $params, 'extras' );
		$distinct = array_get ( $params, 'distinct' );
		$perms = array_get ( $params, 'perms' );
		$debug = array_get ( $params, 'debug' );

		if ( ! is_array ( $tables ) ) $tables = array ( $tables );
		if ( ! is_array ( $fields ) ) $fields = array ( $fields );

		if ( $this->table_prefix )
			foreach ( $tables as $k => $v )
				$tables [ $k ] = $this->table_prefix . $v;

		$select = implode ( ', ', $fields );
		$from = implode ( ', ', $tables );
		$where = '';

		if ( $joins )
			if ( $conds )
				$conds = "$joins AND $conds";
			else
				$conds = $joins;

		if ( $conds ) $where = "WHERE $conds";
		$distinct = $distinct ? 'DISTINCT' : '';
		$sql = "SELECT $distinct $select FROM $from $where $extras";

		return new DBCursor ( $this->db, $sql );
	}

	public function count ( $tables, $fields = '', array $params = array() )
	{
		$fields = 'count(1) as numrows';
		$res = $this->single_row ( $tables, $fields, $params );
		return $res [ 'numrows' ];
	}

	public function all_rows ( $tables, $fields, array $params = array() )
	{
		$cur = $this->select ( $tables, $fields, $params );
		return $cur->fetchAll ();
	}

	public function single_row ( $tables, $fields, array $params = array() )
	{
		$cur = $this->select ( $tables, $fields, $params );
		return $cur->fetchOne ();
	}

	public function insert_id ()
	{
		return $this->db->lastInsertId ();
	}

	private function _escape ( $value )
	{
		if ( $value instanceof DBLiteral ) return $value;
		if ( substr ( $value, 0, 1 ) == '*' ) return substr ( $value, 1 );

		return $this->db->quote ( $value );
	}

	private function _escape_values ( array $values )
	{
		return array_map ( array ( $this, '_escape' ), $values );
	}

	private function _quote ( $col )
	{
		return "`$col`";
	}

	private function _quote_values ( array $values )
	{
		return array_map ( array ( $this, '_quote' ), $values );
	}

	private function _exec ( $sql )
	{
		$res = $this->db->exec ( $sql );
		if ( $res === false )
			throw new DBManagerException ( print_r ( $this->db->errorInfo (), true ) );
		else
			return $res;
	}

	public function insert ( $table, array $data )
	{
		$fields = implode ( ', ', $this->_quote_values ( array_keys ( $data ) ) );
		$values = implode ( ', ', $this->_escape_values ( array_values ( $data ) ) );

		$table = $this->_quote ( $this->table_prefix . $table );

		$sql = "INSERT INTO $table ( $fields ) values ( $values )";

		return $this->_exec ( $sql );
	}

	public function update ( $table, array $data, $conds = null )
	{
		foreach ( $data as $f => $v )
			$fields[] = sprintf ( "%s = %s", $this->_quote ( $f ), $this->_escape ( $v ) );

		$table = $this->_quote ( $this->table_prefix . $table );

		$where = '';
		if ( $conds ) $where = "WHERE $conds";

		$sql = "UPDATE $table SET $fields $where";

		return $this->_exec ( $sql );
	}

	public function delete ( $table, $conds = null )
	{
		$table = $this->_quote ( $this->table_prefix . $table );

		$where = '';
		if ( $conds ) $where = "WHERE $conds";

		$sql = "DELETE FROM $table $where";

		return $this->_exec ( $sql );
	}
}

// {{{ class DBCursor
class DBCursor implements Iterator
{
	private $db;
	private $sql;
	private $pos = 0;
	private $result = null;
	private $rowCount = -1;
	private $fetchMode = PDO::FETCH_ASSOC;

	function __construct ( PDO $db, $sql )
	{
		$this->db = $db;
		$this->sql = $sql;
	}

	private function _fetch_one ()
	{
		$stmt = $this->db->query ( $this->sql );
		$this->result = $stmt->fetch ( $this->fetchMode );
		$this->rowCount = 1;
	}

	private function _fetch_all ()
	{
		$stmt = $this->db->query ( $this->sql );
		$this->result = $stmt->fetchAll ( $this->fetchMode );
		$this->rowCount = count ( $this->result );
	}

	public function fetchAll ()
	{
		$this->_fetch_all ();
		return $this->result;
	}

	public function fetchOne ()
	{
		$this->_fetch_one ();
		return $this->result;
	}

	public function rowCount ()
	{
		return $this->rowCount;
	}

	// {{{ implementation of Iterator

	public function rewind ()
	{
		$this->_fetch_all ();
		$this->pos = 0;
	}

	public function key ()
	{
		return $this->pos;
	}

	public function current ()
	{
		return $this->result [ $this->pos ];
	}

	public function next ()
	{
		$this->pos++;
	}

	public function valid ()
	{
		return array_key_exists ( $this->pos, $this->result );
	}

	// }}}
}
// }}}
